package com.ict.luxshare.jobservice;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;
import android.widget.Toast;

public class ThinBTClient {

    private final ReentrantLock mLock = new ReentrantLock();

    String address;
    String deviceName;

    public final static UUID SerialPortServiceClass_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");  //  SerialPortServiceClass_UUID
    public final static UUID HandsfreeServiceClass_UUID = UUID.fromString("0000111e-0000-1000-8000-00805f9b34fb"); //  HandsfreeServiceClass_UUID
    UUID MY_UUID  = SerialPortServiceClass_UUID;

    int channel = 0;
    private static final String TAG = "THINBTCLIENT";
    private BluetoothAdapter mBluetoothAdapter = null;
    private BluetoothSocket btSocket = null;
    private OutputStream outStream = null;
    private static boolean mExitReader = false;
    private Context context;
    private Handler mUIHandler;

    ThinBTClient(Context context, Handler UIHandler, String szName) {
        this.context = context;
        this.mUIHandler = UIHandler;
        this.deviceName = szName;
    }

    // ==> hardcode your server's MAC address here <==

    public boolean disabled = true;
    private InputStream inputStream;


    /**
     * Called when the activity is first created.
     */

    public boolean isSocketConnected(){
        return btSocket != null;
    }


    @SuppressLint("NewApi")
    synchronized public void connect() {
        if (btSocket != null){
            Log.d(TAG, "Exit connect() because the socket is connected.");
            return;
        }
        if (mLock.isLocked()){
            Log.d(TAG, "Exit connect() because it's locked");
            return;
        }
        mLock.lock();
        Log.d(TAG, "connect()");
        try {
            new Thread(new Runnable() {
                @Override
                synchronized public void run() {
                    try {
                        Log.d(TAG, "+++ DONE IN ON CREATE, GOT LOCAL BT ADAPTER +++");

                        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
                        if (mBluetoothAdapter == null) {
                            toast("Bluetooth is not available.");
                            //finish();
                            disabled = true;
                            return;
                        } else disabled = false;

                        //mBluetoothAdapter.disable();//after closing a stream for some reason it is not getting closed seems a problem in chipset. as a solution i disable and enable adapter
                        //Thread.sleep(200);
                        // maybe sleep requiered here
                        //mBluetoothAdapter.enable();

                        int nTimeout = 50;
                        do{
                            nTimeout--;
                            if (nTimeout <= 0){
                                Log.d(TAG, "wait timeout from connect()");
                                break;
                            }
                            Thread.sleep(1000);
                        }while (!mBluetoothAdapter.isEnabled());
                        //maybe sleep requiered here

                        if(btSocket != null && btSocket.isConnected()){
                            Log.d(TAG, "Exit connect() thread because btSocket is connected already.");
                            return;
                        }

                        Log.d(TAG, "+ ABOUT TO SEARCH FOR A DEVICE +");

                        if (disabled) {
                            //toast("Connection Impossible, No BT Adapter");
                            Log.d(TAG, " BLUETOTH boolean disabled=true ");
                            return;
                        }

                        //need check state better before connecting
                        //http://code.tutsplus.com/tutorials/android-quick-look-bluetoothadapter--mobile-7813

                        if (!mBluetoothAdapter.isEnabled()) {
                            toast("Please enable your BT");
                            disabled = true;
                            //finish();
                            return;
                        }

                        int state = mBluetoothAdapter.getState();
                        if (state == BluetoothAdapter.STATE_TURNING_ON) {
                            toast("Wait. BT State is TURNING ON.");
                            disabled = true;
                            //finish();
                            return;
                        }

                        if (state != BluetoothAdapter.STATE_ON) {
                            toast("BT State is NOT ON");
                            disabled = true;
                            //finish();
                            return;
                        }

                        //get address and UUID for a name
                        boolean found = false;
                        Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
                        for (BluetoothDevice searchdevice : devices) {
                            Log.d(TAG, "+ BLUETOOTH Device: \"" + deviceName + "\" +");
                            if (searchdevice.getName().equals(deviceName)) // Optionally you may filter by UUIDs of services offered by devices
                            {
                                address = searchdevice.getAddress();
                                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
                                    MY_UUID = searchdevice.getUuids()[channel].getUuid();
                                    for (ParcelUuid uuid : searchdevice.getUuids())
                                        Log.d(TAG, "+ BLUETOOTH UUIDs list: " + uuid.getUuid().toString() + " +");
                                }
                                Log.d(TAG, "+ found BLUETOOTH Device " + deviceName + "  UUID " + MY_UUID.toString() + " +");
                                found = true;
                                break;
                            }
                        }

                        if (!found) {
                            Log.d(TAG, "+ " + deviceName + " not found +");
                            toast(deviceName + " not found");
                            disabled = true;
                            return;
                        }


                        Log.d(TAG, "+ ABOUT TO ATTEMPT CLIENT CONNECT +");
                        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

                        if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                            Log.d(TAG, "+ " + deviceName + " not bonded +");
                            toast(deviceName + "not bonded. Conenct " + deviceName + " with phone first");
                            disabled = true;
                            return;
                        }

                        try {
                            if (Build.VERSION.SDK_INT >= 10){
                                final Method method = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[] { UUID.class });
                                btSocket = (BluetoothSocket) method.invoke(device, MY_UUID);
                                Log.d(TAG, "New way create socket.");
                            }else{
                                btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
                                Log.d(TAG, "Old way create socket.");
                            }
                        } catch (IOException e) {
                            Log.d(TAG, "BT: Socket creation failed.", e);
                            toast("BT " + deviceName + " Socket creation failed");
                            disabled = true;
                            return;
                        }

                        if (btSocket == null) {
                            Log.d(TAG, "BT: Socket creation failed.");
                            toast("BT " + deviceName + " Socket creation failed.");
                            disabled = true;
                            return;
                        }

                        mBluetoothAdapter.cancelDiscovery();

                        try {
                            btSocket.connect();
                            Log.d(TAG, "ON RESUME: BT connection established, data transfer link open.");
                        } catch (IOException e) {
                            try {
                                btSocket.close();
                            } catch (IOException e3) {
                                Log.d(TAG,
                                        "ON RESUME: Unable to close socket during connection failure", e3);
                                disabled = true;
                                return;
                            }

                            //toast("Please Disconnect " + deviceName + " from phone in Bluetooth properties of " + deviceName + "");

                            Log.d(TAG,
                                    "ON RESUME: Unable to establish socket connection. Please Disconnect " + deviceName + " from phone in Bluetooth properties of " + deviceName + "", e);
                            disabled = true;
                            return;
                        }


                        Log.d(TAG, "+ ABOUT TO GET INPUT STREAM FROM SERVER +");

                        try {
                            outStream = btSocket.getOutputStream();
                        } catch (IOException e) {
                            Log.d(TAG, "ON RESUME: Output stream creation failed.", e);
                            toast("BT " + deviceName + " Output stream creation failed");
                            disabled = true;
                            return;
                        }

                        Log.d(TAG, "+ ABOUT TO GET OUTPUT STREAM FROM SERVER +");

                        try {
                            inputStream = btSocket.getInputStream();
                        } catch (IOException e) {
                            Log.d(TAG, "ON RESUME: Input stream creation failed.", e);
                            toast("BT " + deviceName + " Input stream creation failed");
                            disabled = true;
                            return;
                        }

                        Log.d(TAG, "+ Creating BT Stream Reader Thread +");
                        if (dataReaderThread != null) {
                            try {
                                mExitReader = true;
                                dataReaderThread.interrupt();
                            } catch (Exception e) {
                            }
                        }
                        dataReaderThread = new Thread(dataReader);
                        if (dataReaderThread == null)
                            return;

                        mExitReader = false;

                        dataReaderThread.start();
                    } catch (Exception e) {
                        Log.d(TAG, "BT Error in Connection thread.", e);
                        toast("BT Error in Connect thread.\n" + e.getStackTrace().toString());
                        disabled = true;
                        return;
                    }
                }

            }).start();
        } catch (Exception e) {
            Log.d(TAG, "BT Error in Connect .", e);
            toast("BT Error in Connection thread.\n" + e.getStackTrace().toString());
            disabled = true;
        }finally {
            mLock.unlock();
            Log.d(TAG, "connect() unlocked!");
        }
    }

    public void ring() {
        send("RING");
    }

    public void send(String message) {
        send(message, true);
    }

    public void send(String message, boolean addNewline) {
        try {
            String logString = message;
            // Create a data stream so we can hear the server.
            if (disabled) {
                Log.d(TAG, "Exit send() because it's disabled.");
                return;
            }

            if (btSocket == null){
                Log.d(TAG, "Exit send() because btSocket is null");
                return;
            }
            if (!btSocket.isConnected()) {
                Log.d(TAG, "BT Send failed. " + deviceName + " is Disconnected.");
                toast("BT Send failed. " + deviceName + " is Disconnected.");
                return;

            }
            if (outStream == null) return;

            //String message = "Hello message from client to server.";
            if (addNewline) message = "\r\n" + message + "\r\n";


            byte[] msgBuffer = message.getBytes();
            try {
                Log.d(TAG, "(send out) > " + logString);
                outStream.write(msgBuffer);
            } catch (IOException e) {
                Log.d(TAG, "BT Exception during write. \"" + message + "\"", e);
                disabled = true;
                onConnectionLost();
            }

        } catch (Exception e) {
            Log.d(TAG, "BT Exception during write. \"" + message + "\"", e);
            disabled = true;
            onConnectionLost();
        }
    }

    public void onData(String line) {
        Log.d(TAG, "(received in) < " + line);
        if (line.indexOf("BRSF") >= 0) {
            send("+BRSF:0");
            send("OK");
        }
        if (line.indexOf("CIND=") >= 0) {
            send("+CIND: (\"service\",(0,1)),(\"call\",(0,1))", true);
            send("OK");
        }
        if (line.indexOf("CIND?") >= 0) {
            send("+CIND: 1,0");
            send("OK");
        }
        if (line.indexOf("CMER") >= 0) {
            send("OK");
        }
        if (line.indexOf("CHLD=?") >= 0) {
            send("+CHLD: 0");
            send("OK");
        }
        if (line.indexOf("CMEE=") >= 0) {
            send("OK");
        }
        if (line.indexOf("CLIP=") >= 0) {
            send("OK");
        }
        if (line.indexOf("CCWA=") >= 0) {
            send("OK");
        }
        if (line.indexOf("NREC=") >= 0) {
            send("OK");
        }
        if (line.indexOf("VGS=") >= 0) {
            send("OK");
        }
        if (line.indexOf("VGM=") >= 0) {
            send("OK");
        }
        if (line.indexOf("XAPL=") >= 0) {
            send("+XAPL= iPhone,1");
            send("OK");
        }
        if (line.indexOf("AT+IPHONEACCEV") >= 0) {
            Pattern p = Pattern.compile("AT\\+IPHONEACCEV=(\\d),(\\d),(\\d)");
            Matcher mMatcher = p.matcher(line);
            mMatcher.matches();
            Log.d(TAG, "Got battery:" + ((Integer.valueOf(mMatcher.group(3)) +1) *10) + "%");
            Message msg = mUIHandler.obtainMessage();
            msg.what = PopupActivity.UPDATE_TITLE;
            Bundle bundle = new Bundle();
            bundle.putInt("Result", ((Integer.valueOf(mMatcher.group(3)) +1) *10));
            msg.setData(bundle);
            mUIHandler.sendMessage(msg);
        }
    }

    public void onConnectionLost() {
        try{
            if (btSocket != null){
                btSocket.close();
                Log.d(TAG, "onConnectionLost, btSocket.close();");
                btSocket = null;
            }
        } catch (IOException e){
            Log.d(TAG, "IOException in onConnectionLost()," + e.toString());
        }
    }

    Thread dataReaderThread = null;

    Runnable dataReader = new Runnable() {
        @Override
        public void run() {
            try {
                // Keep listening to the InputStream while connected
                BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
                String line;

                try {
                    while ((line = in.readLine()) != null && !mExitReader) {
                        onData(line);
                        if (Thread.interrupted()) {
                            Log.d(TAG, "interrupted");
                            disabled = true;
                            onConnectionLost();
                            break;
                        }
                    }
                } catch (IOException e) {
                    Log.d(TAG, "disconnected", e);
                    disabled = true;
                    onConnectionLost();
                }

            } catch (Exception e) {
                Log.d(TAG, "error in datareader thread", e);
                disabled = true;
                onConnectionLost();
            }
        }
    };

    public void onResume() {
        Log.d(TAG, "+ ON RESUME +");
        connect();
    }

    public void toast(final String text) {
        Handler mHandler = new Handler(context.getMainLooper());
        // need to wrap with getMainLooper to run  on service thread.
        // otherwise Toasts fail(and other things that require a looper thread) so to not repeat myself in every callback handling and use when required i just put it here for all callbacks.
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(context, text, Toast.LENGTH_LONG).show();
            }
        });

    }

    public void onPause() {
        Log.d(TAG, "- ON PAUSE -");
        disabled = true;
        if (outStream != null) {
            try {
                outStream.flush();
                Log.d(TAG, "outStream.flush();");
            } catch (IOException e) {
                Log.d(TAG, "ON PAUSE: Couldn't flush output stream.", e);
            }
            try {
                outStream.close();
                Log.d(TAG, "outStream.close();");
            } catch (IOException e) {
                Log.d(TAG, "ON PAUSE: Couldn't close output stream.", e);
            }
            outStream = null;
        }

        if (inputStream != null) {
            try {
                inputStream.close();
                Log.d(TAG, "inputStream.close();");
            } catch (IOException e) {
                Log.d(TAG, "ON PAUSE: Couldn't close input stream.", e);
            }
            inputStream = null;
        }

        if (btSocket != null)
            try {
                btSocket.close();
                btSocket = null;
            } catch (IOException e2) {
                Log.d(TAG, "ON PAUSE: Unable to close socket.", e2);
            }


        if (dataReaderThread != null) {
            try {
                mExitReader = true;
                dataReaderThread.interrupt();
                dataReaderThread = null;
            } catch (Exception e) {
                Log.d(TAG, "Exception on dataReaderThread.interrupt();");
            }
        }
    }
}
